#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_arp.h>
#include <linux/sockios.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "arp.h"
#include "autoip.h"

int pack_sock_fd;

struct in_addr protect_range(struct in_addr ip)
{
	unsigned long int host = inet_lnaof(ip) & IN_CLASSB_HOST;
	if (host == 0) {
		host = 0x1;
	} else if (host == 0xffff) {
		host = 0xfffe;
	}
	ip = inet_makeaddr((IN_CLASSB_NET & LINK_LOCAL_NETADDR), host);
	return ip;
}

int auto_ip(const char *if_name, const char *if_hw_addr)
{
	struct ifreq ifr;	/* interface request struct - if.h */
	struct sockaddr_in *addr;
	int rc;
	int sock_fd;
	int status;
	u_long netmask;
	struct in_addr ip;
	unsigned int rand_num;

	do {
		srand((unsigned)time(NULL));
		rand_num = rand();
		ip = inet_makeaddr((IN_CLASSB_NET & LINK_LOCAL_NETADDR), (IN_CLASSB_HOST & rand_num));
		ip = protect_range(ip);
		status = check_ip_used(ip.s_addr, if_hw_addr);
	} while (status);

	if (status == -1) {
		return (-1);
	}

	netmask = inet_addr("255.255.0.0");

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock_fd < 0) {
		auto_ip_debug("socket(AF_INET, SOCK_DGRAM, 0): err: %d,[%s]\n", errno, strerror(errno));
		return (-1);
	}

	/* order is important. set IP address, and then the netmask */
	strcpy(ifr.ifr_name, if_name);
	ifr.ifr_name[strlen(if_name)] = 0;
	addr = (struct sockaddr_in *)&ifr.ifr_addr;
	bzero(addr, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = ip.s_addr;
	rc = ioctl(sock_fd, SIOCSIFADDR, &ifr);
	if (rc < 0) {
		auto_ip_debug("ioctl(sock_fd, SIOCSIFADDR, &ifr): err: %d,[%s]\n", errno, strerror(errno));
		goto err;
	}

	addr = (struct sockaddr_in *)&ifr.ifr_broadaddr;
	bzero(addr, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = (ip.s_addr & netmask) | (~netmask);
	rc = ioctl(sock_fd, SIOCSIFBRDADDR, &ifr);
	if (rc < 0) {
		auto_ip_debug("ioctl(sock_fd, SIOCSIFBRDADDR, &ifr):err: %d,[%s]\n", errno, strerror(errno));
		goto err;
	}

	addr = (struct sockaddr_in *)&ifr.ifr_netmask;
	bzero(addr, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = netmask;
	rc = ioctl(sock_fd, SIOCSIFNETMASK, &ifr);
	if (rc < 0) {
		auto_ip_debug("ioctl(sock_fd, SIOCSIFNETMASK, &ifr): err: %d,[%s]\n", errno, strerror(errno));
		goto err;
	}

	rc = ioctl(sock_fd, SIOCGIFFLAGS, &ifr);
	if (rc < 0) {
		auto_ip_debug("ioctl(sock_fd, SIOCGIFFLAGS, &ifr): err: %d,[%s]\n", errno, strerror(errno));
		goto err;
	}

	ifr.ifr_flags |= IFF_UP | IFF_BROADCAST | IFF_NOTRAILERS | IFF_RUNNING | IFF_MULTICAST;
	rc = ioctl(sock_fd, SIOCSIFFLAGS, &ifr);
	if (rc < 0) {
		auto_ip_debug("ioctl(sock_fd, SIOCSIFFLAGS, &ifr): err: %d,[%s]\n", errno, strerror(errno));
		goto err;
	}

	close(sock_fd);
	send_gra_arp(ip.s_addr, if_hw_addr);

	return (0);

err:
	close(sock_fd);
	return (-1);

}

int my_gettimeofday(struct timeval *timeval)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	timeval->tv_sec = ts.tv_sec;
	timeval->tv_usec = ts.tv_nsec / 1000;

	return (0);
}

int auto_ip_init(const char *if_name, char *if_hw_addr)
{
	int rc;
	int flags;
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(struct ifreq));
	strcpy(ifr.ifr_name, if_name);
	pack_sock_fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL));
	if (pack_sock_fd == -1) {
		auto_ip_debug("socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL)): err: %d,[%s]\n", errno,
			      strerror(errno));
		return (1);
	}

	rc = fcntl(pack_sock_fd, F_GETFL, 0);
	if (rc < 0) {
		auto_ip_debug("fcntl(pack_sock_fd, F_GETFL, 0): err: %d,[%s]\n", errno, strerror(errno));
		return (-1);
	}
	flags = rc;

	rc = fcntl(pack_sock_fd, F_SETFL, flags | O_NONBLOCK);
	if (rc < 0) {
		auto_ip_debug("fcntl(pack_sock_fd, F_SETFL, flags | O_NONBLOCK): err: %d,[%s]\n", errno,
			      strerror(errno));
		return (-1);
	}

	rc = ioctl(pack_sock_fd, SIOCGIFHWADDR, &ifr);
	if (rc != 0) {
		auto_ip_debug("ioctl(pack_sock_fd, SIOCGIFHWADDR, &ifr): err: %d,[%s]\n", errno, strerror(errno));
		return (-1);
	}

	if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
		auto_ip_debug("Interface %s is not Ethernet or 802.2 Token Ring\n", ifr.ifr_name);
		return (-1);
	}

	memcpy(if_hw_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);

	return (0);
}

int auto_ip_clean()
{
	close(pack_sock_fd);
	return (0);
}

int main(int argc, char **argv)
{

	int rc;
	u_char if_hw_addr[ETH_ALEN];

	if (argc < 2) {
		exit(-1);
	}

	rc = auto_ip_init(argv[1], if_hw_addr);
	if (rc < 0) {
		exit(rc);
	}

	auto_ip(argv[1], if_hw_addr);

	auto_ip_clean();

	return (0);

}
